#ifndef OPTFRAME_NSSEQVRP0R2OPT_HPP_
#define OPTFRAME_NSSEQVRP0R2OPT_HPP_

// Framework includes
#include "../OptFrame/Move.hpp"
#include "../OptFrame/NSSeq.hpp"

using namespace std;

class MoveVRP2OrOpt: public Move<RepPRV, MemPRVP>
{

private:
	int r; // route
	int c; // client
	int pos; // posicao

	PRVPProblemInstance& pPRVP;

public:

	MoveVRP2OrOpt(PRVPProblemInstance& _pPRVP, int _r, int _c, int _pos) :
		pPRVP(_pPRVP), r(_r), c(_c), pos(_pos)
	{
	}

	virtual ~MoveVRP2OrOpt()
	{
	}

	int getRoute()
	{
		return r;
	}

	int getClient()
	{
		return c;
	}

	int getPosition()
	{
		return pos;
	}

	bool canBeApplied(const RepPRV& rep)
	{
		bool all_positive = (r >= 0) && (c >= 0) && (pos >= 0);
		return all_positive && (c != pos) && (c + 1 != pos) && (c + 2 != pos);
	}

	MoveVRP2OrOpt& apply(RepPRV& rep)
	{
		/**int aux_a, aux_b;
		if (c < pos)
		{
			aux_a = rep.at(r).at(c);
			aux_b = rep.at(r).at(c + 1);

			for (int k = c; k < (pos - 1); k++)
				rep.at(r).at(k) = rep.at(r).at(k + 2);

			rep.at(r).at(pos - 1) = aux_a;
			rep.at(r).at(pos) = aux_b;

			return *new MoveVRP2OrOpt(pPRVP, r, pos - 2, c);
		}
		else
		{
			aux_a = rep.at(r).at(c);
			aux_b = rep.at(r).at(c + 1);

			for (int k = c + 1; k > (pos + 2); k--)
				rep.at(r).at(k) = rep.at(r).at(k - 2);

			rep.at(r).at(pos + 1) = aux_a;
			rep.at(r).at(pos + 2) = aux_b;

			return *new MoveVRP2OrOpt(pPRVP, r, pos, c + 2);
		}/**/

		/**/int aux;
		 if (c < pos)
		 {
		 for (int i = c + 1; i < (pos - 1); i++)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i + 1);
		 rep.at(r).at(i + 1) = aux;
		 }
		 for (int i = c; i < (pos - 2); i++)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i + 1);
		 rep.at(r).at(i + 1) = aux;
		 }
		 return *new MoveVRP2OrOpt(pPRVP, r, pos - 2, c);
		 }
		 else
		 {
		 for (int i = c; i > pos; i--)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i - 1);
		 rep.at(r).at(i - 1) = aux;
		 }
		 for (int i = c + 1; i > pos + 1; i--)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i - 1);
		 rep.at(r).at(i - 1) = aux;
		 }
		 return *new MoveVRP2OrOpt(pPRVP, r, pos, c + 2);

		 }/**/

		return *new MoveVRP2OrOpt(pPRVP, -1, -1, -1);
		///exit(1);
	}

	MoveVRP2OrOpt& apply(MemPRVP& m, RepPRV& rep)
	{

		Matrix<double>& dist = (*pPRVP.dist);
		double delta = 0;

		//para facilitar as contas, adicionando o depósito no final da rota
		rep.at(r).push_back(0);

		if (c - 1 < 0)//deve considerar o depósito
			delta += -dist(0, rep.at(r).at(c)) - dist(rep.at(r).at(c + 1), rep.at(r).at(c + 2));
		else
			delta += -dist(rep.at(r).at(c - 1), rep.at(r).at(c)) - dist(rep.at(r).at(c + 1), rep.at(r).at(c + 2));

		if (pos - 1 < 0)
			delta += -dist(0, rep.at(r).at(pos));
		else
			delta += -dist(rep.at(r).at(pos - 1), rep.at(r).at(pos));

		if (c - 1 < 0)
			delta += dist(0, rep.at(r).at(c + 2));
		else
			delta += dist(rep.at(r).at(c - 1), rep.at(r).at(c + 2));

		if (pos - 1 < 0)
			delta += dist(0, rep.at(r).at(c)) + dist(rep.at(r).at(c + 1), rep.at(r).at(pos));
		else
			delta += dist(rep.at(r).at(pos - 1), rep.at(r).at(c)) + dist(rep.at(r).at(c + 1), rep.at(r).at(pos));

		//removendo os depósitos que facilitaram a conta
		rep.at(r).pop_back();
		//setando as variações de fo e inviabilidade
		m.setDelta(delta);
		m.setDelta_inf(0.0);

		/**
		int aux_a, aux_b;
		if (c < pos)
		{
			aux_a = rep.at(r).at(c);
			aux_b = rep.at(r).at(c + 1);

			for (int k = c; k < (pos - 1); k++)
				rep.at(r).at(k) = rep.at(r).at(k + 2);

			rep.at(r).at(pos - 1) = aux_a;
			rep.at(r).at(pos) = aux_b;

			return *new MoveVRP2OrOpt(pPRVP, r, pos - 2, c);
		}
		else
		{
			aux_a = rep.at(r).at(c);
			aux_b = rep.at(r).at(c + 1);

			for (int k = c + 1; k > (pos + 2); k--)
				rep.at(r).at(k) = rep.at(r).at(k - 2);

			rep.at(r).at(pos + 1) = aux_a;
			rep.at(r).at(pos + 2) = aux_b;

			return *new MoveVRP2OrOpt(pPRVP, r, pos, c + 2);
		}/**/

		/**/int aux;
		 if (c < pos)
		 {
		 for (int i = c + 1; i < (pos - 1); i++)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i + 1);
		 rep.at(r).at(i + 1) = aux;
		 }
		 for (int i = c; i < (pos - 2); i++)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i + 1);
		 rep.at(r).at(i + 1) = aux;
		 }
		 return *new MoveVRP2OrOpt(pPRVP, r, pos - 2, c);
		 }
		 else
		 {
		 for (int i = c; i > pos; i--)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i - 1);
		 rep.at(r).at(i - 1) = aux;
		 }
		 for (int i = c + 1; i > pos + 1; i--)
		 {
		 aux = rep.at(r).at(i);
		 rep.at(r).at(i) = rep.at(r).at(i - 1);
		 rep.at(r).at(i - 1) = aux;
		 }
		 return *new MoveVRP2OrOpt(pPRVP, r, pos, c + 2);

		 }/**/

		return *new MoveVRP2OrOpt(pPRVP, -1, -1, -1);
	}
	bool operator==(const Move<RepPRV, MemPRVP>& _m) const
	{
		const MoveVRP2OrOpt& m1 = (const MoveVRP2OrOpt&) _m;
		return (m1.r == r) && (m1.c == c) && (m1.pos == pos);
	}

	void print() const
	{
		cout << "MoveVRP2OrOpt";
		cout << "( " << r << " , " << c << " , " << pos << " )" << endl;
	}
};

class NSIteratorVRP2OrOpt: public NSIterator<RepPRV, MemPRVP>
{

private:

	MoveVRP2OrOpt* m;
	vector<MoveVRP2OrOpt*> moves;
	int index; //index of moves
	const RepPRV& rep;

	PRVPProblemInstance& pPRVP;

public:

	NSIteratorVRP2OrOpt(PRVPProblemInstance& _pPRVP, const RepPRV& _r) :
		pPRVP(_pPRVP), rep(_r)
	{
		m = NULL;
		index = 0;
	}

	virtual ~NSIteratorVRP2OrOpt()
	{
	}

	void first()
	{

		for (int r = 0; r < rep.size(); r++)
		{
			if (rep.at(r).size() > 1)
				for (int c = 0; c < rep.at(r).size() - 1; c++)
				{
					for (int pos = 0; pos <= rep.at(r).size(); pos++)
					{
						if ((c != pos) && (c + 1 != pos) && (c + 2 != pos))
						{
							moves.push_back(new MoveVRP2OrOpt(pPRVP, r, c, pos));
						}
					}
				}
		}

		if (moves.size() > 0)
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	void next()
	{
		index++;
		if (index < moves.size())
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	bool isDone()
	{
		return m == NULL;
	}

	MoveVRP2OrOpt& current()
	{
		if (isDone())
		{
			cout << "There isnt any current element!" << endl;
			cout << "VRP1OrOpt. Aborting." << endl;
			exit(1);
		}

		return *m;
	}
};

class NSSeqVRPOr2Opt: public NSSeq<RepPRV, MemPRVP>
{
	PRVPProblemInstance& pPRVP;

public:

	NSSeqVRPOr2Opt(PRVPProblemInstance& _pPRVP) :
		pPRVP(_pPRVP)
	{
	}

	virtual ~NSSeqVRPOr2Opt()
	{
	}

	MoveVRP2OrOpt& move(const RepPRV& rep)
	{
		int r = rand() % rep.size();

		if (rep.at(r).size() < 3)
			return *new MoveVRP2OrOpt(pPRVP, -1, -1, -1);

		int c = rand() % (rep.at(r).size() - 1);

		int pos;
		do
		{
			pos = rand() % (rep.at(r).size() + 1);
		} while ((c == pos) || (c + 1 == pos) || (c + 2 == pos));

		return *new MoveVRP2OrOpt(pPRVP, r, c, pos);
	}

	virtual NSIteratorVRP2OrOpt& getIterator(const RepPRV& r)
	{
		return *new NSIteratorVRP2OrOpt(pPRVP, r);
	}

	virtual void print()
	{
		cout << "NSSeqVRPOr2Opt" << endl;
	}
};

#endif /*OPTFRAME_NSSEQVRP0R2OPT_HPP_*/
